package frame

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"

	"gitee.com/hasika/gotool/logx/stdlog"

	"gitee.com/hasika/common-server/buffer"
	"gitee.com/hasika/common-server/conn/inet"
)

var Log = stdlog.NewDefaultLog()

type FixSizeFrameReaderWriter struct {
	lenBytesLen int
	maxDataLen  int32
}

func NewFixSizeFrameReader(lenBytesLen int, maxDataLen int32) *FixSizeFrameReaderWriter {
	if lenBytesLen != 2 && lenBytesLen != 4 {
		panic("not supported")
	}
	return &FixSizeFrameReaderWriter{lenBytesLen: lenBytesLen, maxDataLen: maxDataLen}
}

func (s *FixSizeFrameReaderWriter) Return(data *bytes.Buffer) {
	buffer.Return(data)
}

func (s *FixSizeFrameReaderWriter) NonBlockSlicePacketBytes(conn inet.PublicConn) ([][]byte, error) {
	readCtx := conn.GetReadCtx()
	conBuffer := readCtx.Buffer()
	_, err := conBuffer.ReadFromOnce(conn)
	if err != nil {
		return nil, err
	}
	pkgBytes := make([][]byte, 0, 1)
	for {
		if readCtx.IsReadingLen() {
			if conBuffer.Len() < s.lenBytesLen {
				break
			}
			readCtx.SetReadingLen(false)
			dataLen, ok := s.readPacketsLen(conBuffer.Bytes())
			if !ok {
				return nil, nil
			}
			if dataLen > int(s.maxDataLen) {
				uid := uint64(0)
				session := conn.GetSession()
				if session != nil {
					uid = session.Uid
				}
				Log.Errorf("fix size data len %d exceeds maxDataLen %d, address %s ,uid %d", dataLen, s.maxDataLen, conn.GetRemote().String(), uid)
				return nil, errors.New("data too large")
			}
			readCtx.SetDataLen(dataLen)
			conBuffer.Next(s.lenBytesLen)
		}
		if !readCtx.IsReadingLen() {
			dataLen := readCtx.GetDataLen()
			if conBuffer.Len() < dataLen {
				break
			}
			bodyBytes := conBuffer.Next(dataLen)
			contentBuff := make([]byte, dataLen)
			copy(contentBuff, bodyBytes)
			readCtx.SetReadingLen(true)
			pkgBytes = append(pkgBytes, contentBuff)
		}
	}
	return pkgBytes, nil
}

func (s *FixSizeFrameReaderWriter) BlockSlicePacketBytes(conn inet.PublicConn) ([]byte, error) {
	bf := make([]byte, s.lenBytesLen)
	n, err := io.ReadFull(conn, bf)
	if err != nil {
		return nil, err
	}
	if n != s.lenBytesLen {
		return nil, errors.New("read len bytes error")
	}

	dataLen, ok := s.readPacketsLen(bf)
	if !ok {
		return nil, nil
	}
	if dataLen > int(s.maxDataLen) {
		uid := uint64(0)
		session := conn.GetSession()
		if session != nil {
			uid = session.Uid
		}
		Log.Errorf("fix size data len %d exceeds maxDataLen %d, address %s ,uid %d", dataLen, s.maxDataLen, conn.GetRemote().String(), uid)
		return nil, errors.New("data too large")
	}
	dataBuffer := make([]byte, dataLen)
	n, err = io.ReadFull(conn, dataBuffer)
	if err != nil {
		return nil, err
	}
	if n != dataLen {
		return nil, errors.New("read data bytes error")
	}
	return dataBuffer, nil
}

func (s *FixSizeFrameReaderWriter) WriteOnePacketBytes(conn inet.PublicConn, data []byte) error {
	dataLen := len(data)
	totalLen := s.lenBytesLen + dataLen
	unitBuffer := buffer.Allocate(totalLen)
	switch s.lenBytesLen {
	case 2:
		binary.Write(unitBuffer, binary.LittleEndian, uint16(dataLen))
	case 4:
		binary.Write(unitBuffer, binary.LittleEndian, uint32(dataLen))
	}
	unitBuffer.Write(data)
	return conn.GetWriteCtx().DoWrite(conn, unitBuffer)
}

func (s *FixSizeFrameReaderWriter) readPacketsLen(bs []byte) (int, bool) {

	if len(bs) < s.lenBytesLen {
		return 0, false
	}
	switch s.lenBytesLen {
	case 2:
		return int(binary.LittleEndian.Uint16(bs)), true
	case 4:
		return int(binary.LittleEndian.Uint32(bs)), true
	}
	return 0, false
}
